/*
 * Created on Feb 12, 2005
 * Created by dfhuynh
 */
package edu.mit.simile.longwell.schema.impl;

import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.Vector;

import org.apache.log4j.Logger;

import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.Property;
import com.hp.hpl.jena.rdf.model.RDFNode;
import com.hp.hpl.jena.rdf.model.ResIterator;
import com.hp.hpl.jena.rdf.model.Resource;
import com.hp.hpl.jena.rdf.model.Statement;
import com.hp.hpl.jena.rdf.model.StmtIterator;
import com.hp.hpl.jena.vocabulary.RDF;

import edu.mit.simile.longwell.CascadedCache;
import edu.mit.simile.longwell.Profile;
import edu.mit.simile.longwell.jfresnel.FresnelDoc;
import edu.mit.simile.longwell.schema.ISchemaModel;
import edu.mit.simile.longwell.schema.IStructuredClass;
import edu.mit.simile.longwell.schema.IStructuredProperty;
import fr.inria.jfresnel.FresnelDocument;
import fr.inria.jfresnel.Lens;
import fr.inria.jfresnel.fsl.jena.FSLJenaEvaluator;
import fr.inria.jfresnel.jena.JenaLens;

/**
 * @author dfhuynh
 */
public class StructuredClass implements IStructuredClass {
	final static private Logger s_logger = 
		Logger.getLogger(StructuredClass.class);
	
	static protected Map s_uriToRecord = new HashMap();
	static protected Map s_profileToUriToInstance = new HashMap();
	private FresnelDoc fresnelDoc;
	static class Record {
		String		m_uri;
		boolean		m_explicit;
		Set			m_properties = new HashSet();
	}

	final Profile	m_profile;
	final Record	m_record;
	protected Set	m_objects;
	protected Set	m_properties; // cached
	
	StructuredClass(Profile profile, Record record) {
		m_profile = profile;
		m_record = record;
		m_objects = new HashSet();
	}
	
	static public void dispose(Profile profile) {
		s_profileToUriToInstance.remove(profile);
	}
	
	static void load(File file) {
		if (!file.exists()) {
			return;
		}
		
		FileInputStream fis = null;
		try {
			fis = new FileInputStream(file);
			
			ObjectInputStream ois = new ObjectInputStream(fis);
			
			int count = ois.readInt();
			
			for (int i = 0; i < count; i++) {
				Record record = getRecord((String) ois.readObject());

				record.m_explicit = ois.readInt() == 1;

				int propertiesCount = ois.readInt();
				for (int j = 0; j < propertiesCount; j++) {
					record.m_properties.add((String) ois.readObject());
				}
			}
		} catch (EOFException e) {
			s_logger.warn("BUG: We should not get an EOFException. Ignoring for now.");
		} catch (Exception e) {
			s_logger.error(
				"Failed to read schemas from schema registry",
				e);
		} finally {
			if (fis != null) {
				try {
					fis.close();
				} catch (IOException e1) {
				}
			}
		}
	}

	static void save(File file) {
		synchronized (s_uriToRecord) {
			FileOutputStream fos = null;
			ObjectOutputStream oos = null;
			try {
				fos = new FileOutputStream(file);
				oos = new ObjectOutputStream(fos);
				
				Set records = new HashSet(s_uriToRecord.values());
				
				oos.writeInt(records.size());
	
				Iterator i = records.iterator();
				while (i.hasNext()) {
					Record record = (Record) i.next();
					
					oos.writeObject(record.m_uri);
					oos.writeInt(record.m_explicit ? 1 : 0);
					oos.writeInt(record.m_properties.size());
					
					Iterator j = record.m_properties.iterator();
					while (j.hasNext()) {
						String propertyURI = (String) j.next();
						
						oos.writeObject(propertyURI);
					}
				}
			} catch (Exception e) {
				s_logger.error(
					"Failed to write classes to schema registry",
					e);
			} finally {
				if (oos != null) {
					try {
						oos.close();
					} catch (IOException e) {
					}
				}
				if (fos != null) {
					try {
						fos.close();
					} catch (IOException e) {
					}
				}
			}
		}
	}

	static void load(File file, Profile profile) {
		if (!file.exists()) {
			return;
		}
		
		FileInputStream fis = null;
		try {
			fis = new FileInputStream(file);
			
			ObjectInputStream ois = new ObjectInputStream(fis);
			
			Model model = profile.getModel();
			ISchemaModel schemaModel = profile.getSchemaModel();
			
			int count = ois.readInt();
			
			for (int i = 0; i < count; i++) {
				StructuredClass sClass = 
					getStructuredClass(
						profile,
						model.createResource((String) ois.readObject()),
						true
					);
				
				int objectCount = ois.readInt();
				for (int j = 0; j < objectCount; j++) {
					String pseudoURI = (String) ois.readObject();
					
					sClass.m_objects.add(schemaModel.pseudoURIToResource(pseudoURI));
				}
			}
		} catch (EOFException e) {
			s_logger.warn("BUG: We should not get an EOFException. Ignoring for now.");
		} catch (Exception e) {
			s_logger.error(
				"Failed to read profile specific classes from schema registry",
				e);
		} finally {
			if (fis != null) {
				try {
					fis.close();
				} catch (IOException e1) {
				}
			}
		}
	}

	static void save(File file, Profile profile) {
		synchronized (s_profileToUriToInstance) {
			FileOutputStream fos = null;
			ObjectOutputStream oos = null;
			try {
				fos = new FileOutputStream(file);
				oos = new ObjectOutputStream(fos);
				
				Model model = profile.getModel();
				ISchemaModel schemaModel = profile.getSchemaModel();
				
				Map uriToInstance = (Map) s_profileToUriToInstance.get(profile);
				if (uriToInstance != null) {
					Set values = new HashSet(uriToInstance.values());
					
					oos.writeInt(values.size());
					
					Iterator i = values.iterator();
					while (i.hasNext()) {
						StructuredClass sClass = (StructuredClass) i.next();
						
						oos.writeObject(sClass.m_record.m_uri);
		
						oos.writeInt(sClass.m_objects.size());
						
						Iterator j = sClass.m_objects.iterator();
						while (j.hasNext()) {
							Resource r = (Resource) j.next();
							
							oos.writeObject(schemaModel.resourceToPseudoURI(r));
						}
					}
				} else {
					oos.writeInt(0);
				}
			} catch (Exception e) {
				s_logger.error(
					"Failed to write profile specific classes to schema registry",
					e);
			} finally {
				if (oos != null) {
					try {
						oos.close();
					} catch (IOException e) {
					}
				}
				if (fos != null) {
					try {
						fos.close();
					} catch (IOException e) {
					}
				}
			}
		}
	}
	
	static StructuredClass getStructuredClass(
			Profile profile, Resource resource, boolean loadingFromFile) {
		synchronized (s_profileToUriToInstance) {
			Map uriToInstance = (Map) s_profileToUriToInstance.get(profile);
			if (uriToInstance == null) {
				uriToInstance = new HashMap();
				s_profileToUriToInstance.put(profile, uriToInstance);
			}
			
			StructuredClass instance = (StructuredClass)
				uriToInstance.get(resource.getURI());
			
			if (instance == null) {
				Record record = getRecord(resource.getURI());
				
				instance = new StructuredClass(profile, record);
				if (profile.getModel().contains(resource, RDF.type, (RDFNode) null)) {
					instance.m_record.m_explicit = true;
				}
				
				uriToInstance.put(resource.getURI(), instance);
			
				if (!loadingFromFile) {
					instance.onDataAdded(profile.getModel());
				}
			}
			
			return instance;
		}
	}
	
	static Set guessStructuredClassesForProperty(Profile profile, String propertyURI) {
		synchronized (s_profileToUriToInstance) {
			Set sClasses = (Set) s_profileToPropertyToClasses.get(profile, propertyURI);
			if (sClasses == null) {
				sClasses = new HashSet();
				
				Map uriToInstance = (Map) s_profileToUriToInstance.get(profile);
				if (uriToInstance != null) {
					Iterator i = uriToInstance.values().iterator();
					while (i.hasNext()) {
						StructuredClass sClass = (StructuredClass) i.next();
						
						if (sClass.m_record.m_properties.contains(propertyURI)) {
							sClasses.add(sClass);
						}
					}
				}
				
				s_profileToPropertyToClasses.put(profile, propertyURI, sClasses);
			}
			return sClasses;
		}
	}
	static protected CascadedCache s_profileToPropertyToClasses = 
		new CascadedCache(
			StructuredClass.class,
			"profile", 100,
			"property-uri", 20
		);
	
	synchronized public Set onDataAdded(Model model) {
		// BUG: When a tag is added, the type doesn't go along with the object.
		// Consequently, the tag property doesn't get picked up as a property
		// of the class.
		
		ResIterator ri = model.listSubjectsWithProperty(
			RDF.type, model.createResource(m_record.m_uri));
			
		Set properties = new HashSet();
		if (ri.hasNext()) {
			// Caching Strategy: When set changes, replace set
			m_objects = new HashSet(m_objects);
			
			while (ri.hasNext()) {
				Resource r = ri.nextResource();
				
				m_objects.add(r);
				
				StmtIterator si = model.listStatements(r, null, (RDFNode) null);
				while (si.hasNext()) {
					Property p = si.nextStatement().getPredicate();
					if (!p.isAnon()) {
						String uri = p.getURI();
						if (!uri.startsWith(RDF.getURI() + "_")) {
							m_record.m_properties.add(uri);
							properties.add(uri);
						}
					}
				}
			}
			
			if (properties.size() > 0) {
				m_properties = null; // clear cache of properties
				
				s_profileToPropertyToClasses.removeFirstLevel(m_profile);
			}
		}
		
		return properties;
	}
	
	synchronized public boolean onDataRemoved(Model model) {
		ResIterator ri = model.listSubjectsWithProperty(
			RDF.type, 
			model.createResource(m_record.m_uri));
		
		if (ri.hasNext()) {
			// Caching Strategy: When set changes, replace set
			m_objects = new HashSet(m_objects);
			
			while (ri.hasNext()) {
				m_objects.remove(ri.nextResource());
			}
			
			return true;
		} else {
			return false;
		}
	}

	static protected Record getRecord(String uri) {
		synchronized (s_uriToRecord) {
			Record record = (Record) s_uriToRecord.get(uri);
			if (record == null) {
				record = new Record();
				record.m_uri = uri;
				
				s_uriToRecord.put(uri, record);
			}
			return record;
		}
	}
	
	/* (non-Javadoc)
	 * @see edu.mit.simile.longwell.model.IStructuredClass#getURI()
	 */
	synchronized public String getURI() {
		return m_record.m_uri;
	}

	/* (non-Javadoc)
	 * @see edu.mit.simile.longwell.model.IStructuredClass#isExplicit()
	 */
	synchronized public boolean isExplicit() {
		return m_record.m_explicit;
	}

	/* (non-Javadoc)
	 * @see edu.mit.simile.longwell.model.IStructuredClass#getSchemas()
	 */
	synchronized public Set getSchemas() {
		return StructuredSchema.guessStructuredSchemas(m_profile, m_record.m_uri);
	}

	/* (non-Javadoc)
	 * @see edu.mit.simile.longwell.model.IStructuredClass#getProperties()
	 */
	
	private FresnelDocument getFresnelDoc() throws Exception{
		if( fresnelDoc==null){
			Model model = m_profile.getModel();
			fresnelDoc = new FresnelDoc(model);
			FSLJenaEvaluator fje = fresnelDoc.getFresnelEvaluator();
			FresnelDocument fd=null;
			
			try {
				fd = fresnelDoc.getFresnelDocument();
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			return fd;
		}
		else 
			return fresnelDoc.getFresnelDocument();
	}
	
	synchronized public Set getProperties(Set objects) {
		if (m_properties == null || m_properties.size()==0) {
			m_properties = new HashSet();
			
			Iterator i = m_record.m_properties.iterator();
			//-------------FRESNEL FILTER-------------------------------------

			FresnelDocument fd=null;
			
			try {
				fd = getFresnelDoc();
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			Lens[] lenses = fd.getLenses();
			
			
			for(int j=0;j<lenses.length;j++){
				JenaLens ln = (JenaLens) lenses[j];
				String lensClassDomain = ln.getBasicClassDomains()[0];
				String classToEvaluate = m_record.m_uri;
				//System.out.println("lensClassDomain: "+lensClassDomain+" classToEvaluate: "+classToEvaluate);
				if((lensClassDomain).equals(classToEvaluate)){
					String pseudoURI = "";
					Iterator<Object> iter = objects.iterator();
					Resource r = null;
					int count = 0;
					Vector<Statement> stmtVector =null;
					Set<Statement> setProperties = new HashSet<Statement>() ;
					//finding the first 10 resources to load all properties. 
					//If we load only 1 resource maybe that resource doesn't contains all properties. 
					//With 10 we hope to read all of them.
					while(iter.hasNext()){
						pseudoURI = iter.next().toString();
						r=m_profile.getSchemaModel().pseudoURIToResource(pseudoURI);
						if(r.getProperty(RDF.type)!=null){
							String typep = r.getProperty(RDF.type).getObject().toString();
							if(typep.equals(classToEvaluate)){
								stmtVector = ln.getPropertyValuesToDisplay(r, fresnelDoc.getFresnelEvaluator(), null);
								for(int k = 0; k < stmtVector.size(); k++){
									setProperties.add(stmtVector.get(k));
								}
								count++;
							}
							if(count==10)
								break;							
						}
					}
					//Vector<Statement> stmtVector = ln.getPropertyValuesToDisplay(r, fresnelDoc.getFresnelEvaluator(), null);	
					Iterator<Statement> iterStatement = setProperties.iterator();
					while(iterStatement.hasNext()){
						Statement stmt = iterStatement.next();
						stmtVector.add(stmt);
					}
					if(stmtVector!=null){
						s_logger.info("Found " + stmtVector.size() + " properties to display for resource of type " + classToEvaluate);
						for(int k = 0; k < stmtVector.size(); k++){
							Statement stmt = stmtVector.get(k);
							Property prop = stmt.getPredicate();
							StructuredProperty sProperty =
									StructuredProperty.getStructuredProperty(
										m_profile, 
										m_profile.getModel().createProperty(prop.getURI()),
										false);
								if (sProperty != null) {
									m_properties.add(sProperty);
								}
													
							s_logger.info("Property to display: " + prop.toString());
						}			
					}
				}
			}
			//-------------END FRESNEL FILTER---------------------------------
			//if there isn't any lenses for the class, load all properties for that class
			/*
			if (m_properties.isEmpty()){
				while (i.hasNext()) {
					String propertyURI = (String) i.next();
					StructuredProperty sProperty =
						StructuredProperty.getStructuredProperty(
							m_profile, 
							m_profile.getModel().createProperty(propertyURI),
							false);
							
					if (sProperty != null) {
						m_properties.add(sProperty);
					}
				}				
			}	
			*/
		}		
		return m_properties;
	}

	/* (non-Javadoc)
	 * @see edu.mit.simile.longwell.model.IStructuredClass#getProperty(java.lang.String)
	 */
	synchronized public IStructuredProperty getProperty(String uri) {
		synchronized (StructuredClass.class) {
			if (m_record.m_properties.contains(uri)) {
				IStructuredProperty sProperty = 
					StructuredProperty.getStructuredProperty(
						m_profile,
						m_profile.getModel().createProperty(uri),
						false
					);
				
				return sProperty;
			} else {
				return null;
			}
		}
	}

	/* (non-Javadoc)
	 * @see edu.mit.simile.longwell.model.IStructuredClass#getObjects()
	 */
	synchronized public Set getObjects() {
		return m_objects;
	}

	/* (non-Javadoc)
	 * @see edu.mit.simile.longwell.model.IStructuredClass#countObjects()
	 */
	synchronized public int countObjects() {
		return m_objects.size();
	}

	/* (non-Javadoc)
	 * @see edu.mit.simile.longwell.model.IStructuredClass#containsObject(com.hp.hpl.jena.rdf.model.Resource)
	 */
	synchronized public boolean containsObject(Resource object) {
		return m_objects.contains(object);
	}

	@Override
	public Set getProperties() {
		if (m_properties == null) {
			m_properties = new HashSet();
			Iterator i = m_record.m_properties.iterator();
			while (i.hasNext()) {
				String propertyURI = (String) i.next();
				StructuredProperty sProperty =
					StructuredProperty.getStructuredProperty(
						m_profile, 
						m_profile.getModel().createProperty(propertyURI),
						false);
						
				if (sProperty != null) {
					m_properties.add(sProperty);
				}
			}						
		}		
		return m_properties;
	}
}
